#include <QJsonObject>
#include <QJsonArray>

#include <osg/Geometry>

#include <gp_Circ2d.hxx>
#include <GCE2d_MakeArcOfCircle.hxx>
#include <GCE2d_MakeSegment.hxx>
#include <Geom2dAPI_InterCurveCurve.hxx>
#include <GCE2d_MakeCircle.hxx>

#include "Arc2D.h"
#include "JsonReader.h"
#include "GLSLPrograms.h"
#include "Arc2.h"
#include "GetTriangles.h"
#include "OcctOsg.h"
#include "OsgUtil.h"
#include "UCS.h"
#include "Arc3D.h"
#include "Scene.h"

struct ArcPrivate
{
	ArcPrivate()
		: m_rep3D(new osg::Geometry())
	{
		m_rep3D->setNodeMask(D2CurveMask);
		osg::ref_ptr<osg::Program> program = createEdgeProgram();
		auto ss = m_rep3D->getOrCreateStateSet();
		ss->setAttributeAndModes(program, osg::StateAttribute::ON);
	}
	osg::ref_ptr<osg::Geometry> m_rep3D;
	osg::Vec4 m_color = { 1, 1, 1, 1 };
	bool m_selected = false;
	double m_radius = 0;
	double m_startAngle = 0;
	double m_endAngle = 0;
	osg::Vec3d m_center = {0,0,0};
	mutable std::vector<osg::Vec3d> m_handles;
};

bool isCircle(double startAngle, double endAngle)
{
	double error = endAngle - 2 * M_PI;
	return startAngle == 0 && abs(error) < 2.0E-7;
}

Arc2D::Arc2D(INode* parent)
	: Ent2DCurve(parent)
	, d_ptr(new ArcPrivate)
{
}

std::shared_ptr<Ent> Arc2D::clone() const
{
	auto arc = std::make_shared<Arc2D>(getParent());
	arc->copy(*this);
	return arc;
}

void Arc2D::set(const osg::Vec3d& center, const double radius, const double startAngle, const double endAngle)
{
	d_ptr->m_center = center;
	d_ptr->m_radius = radius;
	if (startAngle > endAngle)
		d_ptr->m_startAngle = startAngle - 2 * M_PI;
	else
		d_ptr->m_startAngle = startAngle;
	d_ptr->m_endAngle = endAngle;
	normalize();
	addFlags(UPDATE_GEOMETRY);
}

void Arc2D::setCircle(const osg::Vec3d & center, const double radius)
{
	set(center, radius, 0, 2.0 * M_PI);
}

void Arc2D::setColor(const osg::Vec4 &color)
{
	if (d_ptr->m_color != color)
	{
		d_ptr->m_color = color;
		addFlags(UPDATE_GEOMETRY);
	}
}

const osg::Vec4& Arc2D::color() const
{
	return d_ptr->m_color;
}

const osg::Vec3d& Arc2D::center() const
{
	return d_ptr->m_center;
}

double Arc2D::radius() const
{
	return d_ptr->m_radius;
}

float Arc2D::startAngle() const
{
	return d_ptr->m_startAngle;
}

float Arc2D::endAngle() const
{
	return d_ptr->m_endAngle;
}

void Arc2D::update3D()
{
	if (flags())
	{
		const osg::Vec4f& selectedColor = osg::Vec4f(0.f, 0.8f, 0.f, 1.f);
		auto va = new osg::Vec3Array();
		auto points = getArcPoints(d_ptr->m_startAngle, d_ptr->m_endAngle, d_ptr->m_radius, d_ptr->m_center, osg::Vec3d(0, 0, 1), 3);
		appendPoints(va, points);
		d_ptr->m_rep3D->setVertexArray(va);

		osg::ref_ptr<osg::Vec4Array> ca = new osg::Vec4Array();
		
		if (d_ptr->m_selected)
			ca->push_back(selectedColor);
		else
			ca->push_back(d_ptr->m_color);
		
		d_ptr->m_rep3D->setColorArray(ca.get(), osg::Array::BIND_OVERALL);

		d_ptr->m_rep3D->getPrimitiveSetList().clear();
		d_ptr->m_rep3D->addPrimitiveSet(new osg::DrawArrays(osg::PrimitiveSet::LINE_STRIP, 0, va->size()));
		
		calHandles();
		d_ptr->m_rep3D->dirtyGLObjects();
		removeFlags(0xFFFFFFFF);
	}
}

osg::Node * Arc2D::getRep3D() const
{
	return d_ptr->m_rep3D.get();
}

void Arc2D::setSelected(bool val)
{
	if (d_ptr->m_selected != val)
	{
		d_ptr->m_selected = val;
		addFlags(UPDATE_GEOMETRY);
	}
}

void Arc2D::save(QJsonObject & json) const
{
	Ent2DCurve::save(json);

	json["radius"] = d_ptr->m_radius;
	json["startAngle"] = d_ptr->m_startAngle;
	json["endAngle"] = d_ptr->m_endAngle;
	json["center"] = saveVec3f(d_ptr->m_center);
	json["color"] = saveVec4f(d_ptr->m_color);
}

void Arc2D::load(const QJsonObject& json, JsonReader& reader)
{
	Ent2DCurve::load(json, reader);

	d_ptr->m_radius = json["radius"].toDouble();
	d_ptr->m_startAngle = json["startAngle"].toDouble();
	d_ptr->m_endAngle = json["endAngle"].toDouble();
	d_ptr->m_center = loadVec3f(json["center"].toArray());
	d_ptr->m_color = loadVec4f(json["color"].toArray());
}

osg::Vec3d getPointOfAngle(const double angle,const osg::Vec3d& center,const double radius)
{
	const double sinA = sin(angle);
	const double cosA = cos(angle);

	const double dx = center.x() + radius * cosA;
	const double dy = center.y() + radius * sinA;
	osg::Vec3d pt(dx, dy, center.z());
	return pt;
}

void Arc2D::calHandles() const
{
	d_ptr->m_handles.clear();
	if (isCircle(d_ptr->m_startAngle, d_ptr->m_endAngle))
	{
		d_ptr->m_handles.push_back(d_ptr->m_center + osg::Vec3d(d_ptr->m_radius, 0, 0));
		d_ptr->m_handles.push_back(d_ptr->m_center + osg::Vec3d(0, d_ptr->m_radius, 0));
		d_ptr->m_handles.push_back(d_ptr->m_center - osg::Vec3d(d_ptr->m_radius, 0, 0));
		d_ptr->m_handles.push_back(d_ptr->m_center - osg::Vec3d(0, d_ptr->m_radius, 0));
		d_ptr->m_handles.push_back(d_ptr->m_center);
	}
	else
	{
		const double midAngle = (d_ptr->m_startAngle + d_ptr->m_endAngle) / 2;
		osg::Vec3d startPt = getPointOfAngle(d_ptr->m_startAngle, d_ptr->m_center, d_ptr->m_radius);
		osg::Vec3d midPt = getPointOfAngle(midAngle, d_ptr->m_center, d_ptr->m_radius);
		osg::Vec3d endPt = getPointOfAngle(d_ptr->m_endAngle, d_ptr->m_center, d_ptr->m_radius);

		d_ptr->m_handles.push_back(startPt);
		d_ptr->m_handles.push_back(midPt);
		d_ptr->m_handles.push_back(endPt);
		d_ptr->m_handles.push_back(d_ptr->m_center);
	}
}

std::vector<osg::Vec3d> Arc2D::handles() const
{
	if (flags())
	{
		calHandles();
	}

	return d_ptr->m_handles;
}

void Arc2D::move(const osg::Vec3d& offset)
{
	d_ptr->m_center += offset;
	addFlags(UPDATE_GEOMETRY);
}

void Arc2D::scale(const osg::Vec3d & center, double scale)
{
	d_ptr->m_center = center + (d_ptr->m_center - center) * scale;
	d_ptr->m_radius = d_ptr->m_radius * scale;
}

void Arc2D::mirror(const osg::Vec3d & start, const osg::Vec3d & end)
{
	d_ptr->m_center = mirror2D(start,end, d_ptr->m_center);
	double axisAng = (double)atan2(end.y() - start.y(), end.x() - start.x());
	d_ptr->m_startAngle = 2 * axisAng - d_ptr->m_startAngle;
	d_ptr->m_endAngle = 2 * axisAng - d_ptr->m_endAngle;
	std::swap(d_ptr->m_startAngle, d_ptr->m_endAngle);
	while (d_ptr->m_endAngle < d_ptr->m_startAngle)
	{
		d_ptr->m_endAngle += 2 * M_PI;
	}
	normalize();
	addFlags(UPDATE_GEOMETRY);
}

void Arc2D::stretch(int index, const osg::Vec3d &offset)
{
	if (d_ptr->m_handles.empty())
		calHandles();
	if (isCircle(d_ptr->m_startAngle, d_ptr->m_endAngle))
	{
		if (index == 4)
		{
			move(offset);
		}
		else
		{
			auto point = d_ptr->m_handles[index] + offset;
			d_ptr->m_radius = (point - d_ptr->m_center).length();
			addFlags(UPDATE_GEOMETRY);
		}
	}
	else
	{
		if (index == 3)
		{
			move(offset);
		}
		else
		{
			d_ptr->m_handles[index] = d_ptr->m_handles[index] + offset;
			const osg::Vec3d startPt = d_ptr->m_handles[0];
			const osg::Vec3d secondPt = d_ptr->m_handles[1];
			const osg::Vec3d endPt = d_ptr->m_handles[2];
			CArc2 arc(
				CDblPoint(startPt.x(), startPt.y()),
				CDblPoint(secondPt.x(), secondPt.y()),
				CDblPoint(endPt.x(), endPt.y())
			);
			set(osg::Vec3d(arc.center.x, arc.center.y, 0), arc.radius, arc.StartAngle(), arc.EndAngle());
			addFlags(UPDATE_GEOMETRY);
		}
	}
}

void Arc2D::copy(const Ent& ent)
{
	auto arc = dynamic_cast<const Arc2D*>(&ent);
	if (arc)
	{
		d_ptr->m_color = arc->color();
		d_ptr->m_startAngle = arc->startAngle();
		d_ptr->m_endAngle = arc->endAngle();
		d_ptr->m_center = arc->center();
		d_ptr->m_radius = arc->radius();
		d_ptr->m_handles = arc->handles();
		addFlags(UPDATE_GEOMETRY);
	}
}

void Arc2D::rotate(const double angle, const osg::Vec2d & basePoint)
{
	d_ptr->m_startAngle += angle;
	d_ptr->m_endAngle += angle;
	normalize();
	rotate2D(basePoint.x(), basePoint.y(), angle, d_ptr->m_center);
	addFlags(UPDATE_GEOMETRY);
}

std::vector<Handle(Geom2d_Curve)> Arc2D::getCurve2d() const
{
	std::vector<Handle(Geom2d_Curve)> result;
	if (d_ptr->m_endAngle > d_ptr->m_startAngle)
	{
		gp_Circ2d circ;
		circ.SetLocation(toPnt2d(d_ptr->m_center));
		circ.SetRadius(d_ptr->m_radius);
		if (std::abs(d_ptr->m_endAngle - d_ptr->m_startAngle - M_PI*2) < 1e-5)
		{
			GCE2d_MakeCircle maker(circ);
			if (maker.IsDone())
			{
				Handle(Geom2d_Curve) curve = maker.Value();
				result.push_back(new Geom2d_TrimmedCurve(curve,curve->FirstParameter(),curve->LastParameter()));
			}
		}
		else
		{
			GCE2d_MakeArcOfCircle maker(circ, d_ptr->m_startAngle, d_ptr->m_endAngle);
			if (maker.IsDone())
				result.push_back(maker.Value());
		}	
	}
	return result;
}

std::shared_ptr<Ent2DCurve> Arc2D::extend(bool front, double param, const gp_Pnt2d& point) const
{
	auto cloneEnt = std::static_pointer_cast<Arc2D>(clone());
	if (front)
	{
		cloneEnt->d_ptr->m_startAngle = param;
		while (cloneEnt->d_ptr->m_startAngle > cloneEnt->d_ptr->m_endAngle)
			cloneEnt->d_ptr->m_endAngle += 2 * M_PI;
		while (cloneEnt->d_ptr->m_endAngle - cloneEnt->d_ptr->m_startAngle - 2 * M_PI > 1.0E-7)
			cloneEnt->d_ptr->m_startAngle += 2 * M_PI;
		cloneEnt->normalize();
	}
	else
	{
		cloneEnt->d_ptr->m_endAngle = param;
		while (cloneEnt->d_ptr->m_startAngle > cloneEnt->d_ptr->m_endAngle)
			cloneEnt->d_ptr->m_startAngle -= 2 * M_PI;
		while (cloneEnt->d_ptr->m_endAngle - cloneEnt->d_ptr->m_startAngle - 2 * M_PI > 1.0E-7)
			cloneEnt->d_ptr->m_endAngle -= 2 * M_PI;
		cloneEnt->normalize();
	}
	return cloneEnt;
}

std::vector<std::shared_ptr<Ent2DCurve>> Arc2D::trim(const SectionParam& from, const SectionParam& to) const
{
	std::vector<std::shared_ptr<Ent2DCurve>> result;
	if (from.param == d_ptr->m_startAngle && to.param == d_ptr->m_endAngle)
		return result;
	if (isCircle(startAngle(), endAngle()))
	{
		auto cloneEnt = std::static_pointer_cast<Arc2D>(clone());
		cloneEnt->d_ptr->m_startAngle = to.param;
		cloneEnt->d_ptr->m_endAngle = from.param;
		while (cloneEnt->d_ptr->m_startAngle > cloneEnt->d_ptr->m_endAngle)
			cloneEnt->d_ptr->m_startAngle -= 2 * M_PI;
		while (cloneEnt->d_ptr->m_endAngle - cloneEnt->d_ptr->m_startAngle > 2 * M_PI)
			cloneEnt->d_ptr->m_endAngle -= 2 * M_PI;
		cloneEnt->normalize();
		result.push_back(cloneEnt);
		return result;
	}
	auto curve2ds = getCurve2d();
	SectionParam start, end;
	getStartEndParam(curve2ds, start, end);
	if (start < from)
	{
		auto cloneEnt = std::static_pointer_cast<Arc2D>(clone());
		cloneEnt->d_ptr->m_endAngle = from.param;
		while (cloneEnt->d_ptr->m_startAngle > cloneEnt->d_ptr->m_endAngle)
			cloneEnt->d_ptr->m_startAngle -= 2 * M_PI;
		while (cloneEnt->d_ptr->m_endAngle - cloneEnt->d_ptr->m_startAngle > 2 * M_PI)
			cloneEnt->d_ptr->m_endAngle -= 2 * M_PI;
		cloneEnt->normalize();
		result.push_back(cloneEnt);
	}
	if (to < end)
	{
		auto cloneEnt = std::static_pointer_cast<Arc2D>(clone());
		cloneEnt->d_ptr->m_startAngle = to.param;
		while (cloneEnt->d_ptr->m_startAngle > cloneEnt->d_ptr->m_endAngle)
			cloneEnt->d_ptr->m_endAngle += 2 * M_PI;
		while (cloneEnt->d_ptr->m_endAngle - cloneEnt->d_ptr->m_startAngle > 2 * M_PI)
			cloneEnt->d_ptr->m_endAngle -= 2 * M_PI;
		cloneEnt->normalize();
		result.push_back(cloneEnt);
	}
	return result;
}

void Arc2D::normalize()
{
	while (d_ptr->m_endAngle < 0)
	{
		d_ptr->m_startAngle += 2 * M_PI;
		d_ptr->m_endAngle += 2 * M_PI;
	}
	if (std::abs(d_ptr->m_endAngle - d_ptr->m_startAngle - 2 * M_PI) < 1.0E-7)
	{
		d_ptr->m_startAngle = 0;
		d_ptr->m_endAngle = 2 * M_PI;
	}
}

bool Arc2D::offset(double offset)
{
	osg::Vec3d center = d_ptr->m_center;
	double radius = d_ptr->m_radius;
	radius += offset;
	if (radius < 0)
		return false;
	d_ptr->m_radius = radius;
	addFlags(UPDATE_GEOMETRY);
	return true;
}

bool Arc2D::offset(double offset, const osg::Vec3d & offsetSide)
{
	auto curves = getCurve2d();
	auto curve = curves[0];
	gp_Pnt2d start, end;
	gp_Vec2d startTangentV, endTangentV;
	curve->D1(curve->FirstParameter(), start, startTangentV);
	curve->D1(curve->LastParameter(), end, endTangentV);
	gp_Vec2d startV(start, toPnt2d(offsetSide));
	gp_Vec2d endV(end, toPnt2d(offsetSide));
	if ((startTangentV ^ startV) > 0 && (endTangentV ^ endV) > 0)
	{
		GCE2d_MakeSegment maker(toPnt2d(offsetSide), toPnt2d(d_ptr->m_center));
		Handle(Geom2d_Curve) lineCurve;
		if (maker.IsDone())
		{
			lineCurve = maker.Value();
			Geom2dAPI_InterCurveCurve inter;
			inter.Init(lineCurve, curve);
			osg::Vec3d interPt;
			if (inter.NbPoints() == 0)
			{
				offset = -offset;
			}
		}
	}
	double radius = d_ptr->m_radius;
	radius += offset;
	if (radius < 0)
		return false;
	d_ptr->m_radius = radius;
	addFlags(UPDATE_GEOMETRY);
	return true;
}

std::vector<std::shared_ptr<Ent3DCurve>> Arc2D::to3d()
{
	std::vector<std::shared_ptr<Ent3DCurve>> result;
	UCS* ucs = static_cast<UCS*>(getParent());
	auto matrix = ucs->getMatrix();
	osg::Vec3d center = osg::Vec3d(d_ptr->m_center.x(),d_ptr->m_center.y(),0);
	center = center * matrix;
	Scene* scene = static_cast<Scene*>(getRoot());
	auto arc = std::make_shared<Arc3D>(scene);
	arc->setOrigin(center);
	arc->setAxis(ucs->xAxis(),ucs->yAxis());
	arc->set(osg::Vec3d(), d_ptr->m_radius, d_ptr->m_startAngle, d_ptr->m_endAngle);
	result.push_back(arc);
	return result;
}
